package hotupdate;

import java.io.*;
import java.util.Objects;
import java.util.Set;

/**
 * 热加载类文件生成目录
 */
public class CreateReloadDir {

    private String BASE_DIR = "D:\\hotDir\\";
    private Set<String> classStrList;

    public CreateReloadDir(Set<String> classStrList, String baseDir) {
        this.classStrList = classStrList;
        BASE_DIR = baseDir;
    }

    public void run() {
        if (this.classStrList.isEmpty()) {
            return;
        }
        File baseDir = new File(BASE_DIR);
        if (baseDir.exists() && baseDir.isDirectory()) {
            deleteFile(baseDir);
        }
        baseDir.mkdir();
        try {
            for (String clazz : this.classStrList) {
                Class<?> targetClass = Class.forName(clazz);
                if (Objects.isNull(targetClass)) {
                    continue;
                }
                //class基类基础路径
                doExecuteContinue(targetClass);
                //判断是否包含内部类，最多支持100个
                for (int i = 1; i <= 100; i++) {
                    Class<?> targetInnerClass;
                    try {
                        targetInnerClass = Class.forName(clazz + "$" + i);
                    } catch (Exception e) {
                        break;//没找到，跳出
                    }
                    if (Objects.isNull(targetInnerClass)) {
                        break;
                    }
                    doExecuteContinue(targetInnerClass);
                }
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        //zip
        try {
            File jarFile = new File(BASE_DIR + "\\" + "hotupdate.jar");
            if (jarFile.exists()) {
                jarFile.delete();
                createEmpty(jarFile);
            }
            FileOutputStream jos = new FileOutputStream(jarFile);
            JarCreate.toJar(BASE_DIR + "\\" + "com", jos, true);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void doExecuteContinue(Class<?> targetClass) throws IOException {
        String clsBasePath = getClassAbsPath(targetClass);
        String[] dirs = targetClass.getPackage().getName().split("\\.");
        //本地打包目录
        String dir = BASE_DIR;
        //class类包路径
        String clsPath = "";
        for (int i = 0; i < dirs.length; i++) {
            dir = dir + "\\" + dirs[i];
            clsPath = clsPath + "\\" + dirs[i];
            File dirFile = new File(dir);
            if (!dirFile.exists()) {
                dirFile.mkdir();
            }
        }
        String finalDir = dir;
        String simpleName = targetClass.getSimpleName();
        if (simpleName.length() == 0) {
            simpleName = targetClass.getName().substring(targetClass.getName().lastIndexOf(".") + 1);
        }
        String clsName = clsPath + "\\" + simpleName + ".class";
        copy(clsBasePath + "\\" + clsName, finalDir + "\\" + simpleName + ".class");
    }

    /**
     * 获取类的绝对路径
     */
    private static String getClassAbsPath(Class cls) {
        if (cls == null) {
            throw new java.lang.IllegalArgumentException("参数为空");
        }
        ClassLoader loader = cls.getClassLoader();
        //获得类的全名，包括包名
        String clsName = cls.getName() + ".class";
        //获得传入参数所在的包
        Package pack = cls.getPackage();
        String path = "";
        if (pack != null) {
            String packName = pack.getName();
            //此处简单判定是否是java基础库，防止用户传入JDK内置的类库
            if (packName.startsWith("java.") || packName.startsWith("javax.")) {
                throw new java.lang.IllegalArgumentException("禁止传入系统类");
            }
            //在类的名称中，去掉包名的部分，获得类的文件名
            clsName = clsName.substring(packName.length() + 1);
            //判定包名是否是简单包名，如果是，直接将包名转为路径
            if (packName.indexOf(".") < 0) {
                path = packName + "/";
            } else {
                //否则按照包名的组成部分，将包名转换为路径
                int start = 0, end = 0;
                end = packName.indexOf(".");
                while (end != -1) {
                    path = path + packName.substring(start, end) + "/";
                    start = end + 1;
                    end = packName.indexOf(".", start);
                }
                path = path + packName.substring(start) + "/";
            }
        }
        //调用ClassLoader的getResource方法传入包含路径信息的类文件名
        java.net.URL url = loader.getResource(path + clsName);
        //从URL对象中获取路径信息
        String realPath = url.getPath();
        //去掉路径信息中的协议名“file:”
        int pos = realPath.indexOf("file:");
        if (pos > -1) {
            realPath = realPath.substring(pos + 5);
        }
        //去掉路径信息最后包含类文件信息的部分，得到类所在路径
        pos = realPath.indexOf(path + clsName);
        realPath = realPath.substring(0, pos - 1);
        //如果类文件被打包到jar等文件中时，去掉对应的jar等打包文件名
        if (realPath.endsWith("!")) {
            realPath = realPath.substring(0, realPath.lastIndexOf("/"));
        }
        try {
            realPath = java.net.URLDecoder.decode(realPath, "utf-8");
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
        return realPath;
    }

    /**
     * 拷贝文件
     */
    private static void copy(String src, String target) throws IOException {
        File source = new File(src);
        if (!source.exists()) {
            System.out.println("copy 文件未找到" + src);
            return;
        }
        File dest = new File(target);
        if (dest.exists()) {
            dest.delete();
            createEmpty(dest);
        }
        InputStream input = null;
        OutputStream output = null;
        try {
            input = new FileInputStream(source);
            output = new FileOutputStream(dest);
            byte[] buff = new byte[2048];
            int bytesRead;
            while ((bytesRead = input.read(buff)) > 0) {
                output.write(buff, 0, bytesRead);
            }
        } finally {
            input.close();
            output.close();
        }
    }

    /**
     * 创建空文件
     */
    private static void createEmpty(File file) throws IOException {
        RandomAccessFile raf = null;
        try {
            raf = new RandomAccessFile(file, "rws");
            raf.setLength(0);
        } finally {
            try {
                raf.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 删除文件
     */
    private void deleteFile(File fileDelete) {
        File[] listFiles = fileDelete.listFiles();
        for (File listFile : listFiles) {
            if (listFile.isDirectory()) {
                deleteFile(listFile);
            } else {
                listFile.delete();
            }
        }
    }
}
